package com.bss.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.andy.basics.Constants;
import com.andy.basics.Dao;
import com.andy.basics.GenericDao;
import com.bss.dao.AdmissionDao;
import com.bss.dao.BedBookingDao;
import com.bss.dao.CurrentAttendentDao;
import com.bss.dao.PaymentDao;
import com.bss.dao.PaymentTypeDao;
import com.bss.dao.ReceiptDao;
import com.bss.dao.RoomBookingDao;
import com.bss.model.Admission;
import com.bss.model.BedBooking;
import com.bss.model.CurrentAttendent;
import com.bss.model.Payment;
import com.bss.model.PaymentType;
import com.bss.model.Receipt;
import com.bss.model.RoomBooking;

@Service
@Scope("session")
public class PaymentService extends com.andy.basics.Service<Payment> {

	private static final String CLASS = PaymentService.class.getName();

	private static final Logger TRACE = Logger.getLogger(Constants.TRACE_KEY + PaymentService.CLASS);

	private static final Logger DEBUG = Logger.getLogger(Constants.DEBUG_KEY + PaymentService.CLASS);

	private static final Logger AUDIT = Logger.getLogger(Constants.AUDIT_KEY + PaymentService.CLASS);

	private static final Logger ERROR = Logger.getLogger(Constants.ERROR_KEY + PaymentService.CLASS);

	@Autowired
	private PaymentDao paymentDao;

	@Autowired
	private RoomBookingDao roomBookingDao;

	@Autowired
	private BedBookingDao bedBookingDao;

	@Autowired
	private PaymentTypeDao paymentTypeDao;

	@Autowired
	private CurrentAttendentDao currentAttendentDao;

	@Autowired
	private GenericDao genericDao;

	@Autowired
	private ReceiptDao receiptDao;

	private Admission admission;

	private DataModel roomBookings;

	private DataModel bedBookings;

	private List<Receipt> receipts;

	private DataModel receiptsModel;

	private List<Payment> donations;

	private List<Payment> securityDeposits;

	private List<Payment> utensilDeposits;

	@Autowired
	private AdmissionService admissionService;

	@Autowired
	private AdmissionDao admissionDao;

	private Receipt selectedReceipt;

	@Autowired
	private DailyDepositAndRefundService dailyDepositAndRefundService;

	@Override
	public Dao<Payment> getDao() {
		return this.paymentDao;
	}

	public DataModel getRoomBookings() {
		return this.roomBookings;
	}

	public void setBedBookings(final DataModel bedBookings) {
		this.bedBookings = bedBookings;
	}

	public DataModel getBedBookings() {
		return this.bedBookings;
	}

	public void setRoomBookings(final DataModel roomBookings) {
		this.roomBookings = roomBookings;
	}

	public void setAdmission(final Admission admission) {
		this.admission = admission;
	}

	public Admission getAdmission() {
		if (this.admission == null) {
			this.admission = new Admission();
		}
		return this.admission;
	}

	@Transactional
	public void accept(final Admission admission, final Receipt receipt) {

		for (RoomBooking roomBooking : admission.getRoomBookings()) {
			if (roomBooking.isSelected()) {
				System.out.println("Paid " + roomBooking.getPayment() + " for " + roomBooking.getRoom().getRoomNumber());

				Payment payment = new Payment();
				payment.setAmount(roomBooking.getPayment());
				payment.setPaymentType(this.paymentTypeDao.find("name='Room Rent'").get(0));
				payment.setReceipt(this.reloadReceipt(receipt));
				payment.setPayable(roomBooking);
				this.paymentDao.persist(payment);
				roomBooking.setTotalPayment((roomBooking.getTotalPayment() == null ? 0 : roomBooking.getTotalPayment())
						+ payment.getAmount());

				this.roomBookingDao.update(roomBooking);
			}
		}

		for (BedBooking bedBooking : admission.getBedBookings()) {
			if (bedBooking.isSelected()) {
				System.out.println("Paid " + bedBooking.getPayment() + " for " + bedBooking.getBed().getBedNumber());

				Payment payment = new Payment();
				payment.setAmount(bedBooking.getPayment());
				payment.setPaymentType(this.paymentTypeDao.find("name='Dormitory Rent'").get(0));
				payment.setReceipt(this.reloadReceipt(receipt));
				payment.setPayable(bedBooking);
				this.paymentDao.persist(payment);

				bedBooking.setTotalPayment((bedBooking.getTotalPayment() == null ? 0 : bedBooking.getTotalPayment())
						+ payment.getAmount());
				this.bedBookingDao.update(bedBooking);
			}
		}

	}

	protected Receipt reloadReceipt(final Receipt receipt) {
		return receipt.getId() == 0 ? receipt : this.receiptDao.findById(receipt.getId());
	}

	@Transactional
	public void populate(final Date date) {
		this.populate(this.getAdmission(), date);
	}

	@Transactional
	public void populate(final Admission adm, final Date date) {

		Admission admission = this.populateOnly(adm, date);

		if (admission == null) {
			return;
		}

		this.setRoomBookings(new ListDataModel(admission.getRoomBookings()));
		this.setBedBookings(new ListDataModel(admission.getBedBookings()));

		this.donations = new ArrayList<Payment>();
		this.securityDeposits = new ArrayList<Payment>();
		this.utensilDeposits = new ArrayList<Payment>();

		for (Payment payment : admission.getPayments()) {
			if ("Donation".equals(payment.getPaymentType().getName())) {
				this.donations.add(payment);
			} else if ("Security Deposit".equals(payment.getPaymentType().getName())) {
				this.securityDeposits.add(payment);
			} else if ("Utensil Deposit".equals(payment.getPaymentType().getName())) {
				this.utensilDeposits.add(payment);
			}
		}

		this.initReceipts(admission);
		this.setAdmission(admission);
	}

	@Transactional
	public Admission populateOnly(final Admission adm, final Date date) {

		Admission admission = this.admissionDao.findById(adm.getId());

		if (admission == null) {
			return null;
		}

		List<CurrentAttendent> attendents = this.currentAttendentDao
				.find("dateOfArrival is not null and dateOfLeave is null and admission.id=" + admission.getId());
		admission.setCurrentAttendents(attendents);

		this.getGenericDao().initialize(admission.getBedBookings());
		this.getGenericDao().initialize(admission.getRoomBookings());
		this.getGenericDao().initialize(admission.getMaterialAllocations());
		this.getGenericDao().initialize(admission.getPayments());

		for (RoomBooking roomBooking : admission.getRoomBookings()) {

			this.getGenericDao().initialize(roomBooking.getPayments());

			Integer totalPaid = roomBooking.getTotalPayment();
			if (totalPaid == null) {
				totalPaid = 0;
			}
			Integer rate = roomBooking.getRate();
			if (rate == null) {
				rate = 0;
			}
			int tobePaid = rate * this.getUtils().findDifferenceInDays(roomBooking.getCheckInDate(), date);
			roomBooking.setPendingPayment(tobePaid - totalPaid);
		}

		for (BedBooking bedBooking : admission.getBedBookings()) {

			this.getGenericDao().initialize(bedBooking.getPayments());

			Integer totalPaid = bedBooking.getTotalPayment();
			if (totalPaid == null) {
				totalPaid = 0;
			}
			Integer rate = bedBooking.getRate();
			if (rate == null) {
				rate = 0;
			}
			int tobePaid = rate * this.getUtils().findDifferenceInDays(bedBooking.getCheckInDate(), date);
			bedBooking.setPendingPayment(tobePaid - totalPaid);
		}

		return admission;
	}

	public void initReceipts(Admission admission) {
		this.receipts = this.receiptDao.findReceipts(admission);
		this.setReceiptsModel(new ListDataModel(this.receipts));
	}

	@Override
	public GenericDao getGenericDao() {
		return this.genericDao;
	}

	public void setReceipts(final List<Receipt> receipts) {
		this.receipts = receipts;
	}

	public List<Receipt> getReceipts() {
		return this.receipts;
	}

	@Transactional
	public void donate(final Admission admission, final int amount, final Receipt receipt) {

		Payment payment = new Payment();
		payment.setAmount(amount);
		payment.setPaymentType(this.paymentTypeDao.find("name='Donation'").get(0));
		payment.setReceipt(this.reloadReceipt(receipt));
		payment.setPayable(admission);
		this.paymentDao.persist(payment);

		this.admissionDao.update(admission);
	}

	public List<Payment> getDonations() {
		return this.donations;
	}

	public List<Payment> getSecurityDeposits() {
		return this.securityDeposits;
	}

	public List<Payment> getUtensilDeposits() {
		return this.utensilDeposits;
	}

	public boolean allowDeposit(final String type) {
		Admission admission = this.getAdmission();
		if (admission.isExitInited() && !("Donation".equals(type))) {
			PaymentService.DEBUG.debug("Exit has inited for the patient " + admission.getPatient().getName() + ", " + type
					+ " not allowed !");
			return false;
		}
		if (admission.getPayments() != null) {
			for (Payment payment : admission.getPayments()) {
				if (payment.getPaymentType().getName().equals(type)) {
					return false;
				}
			}
		}
		return true;
	}

	public boolean allowRefund(final String type) {
		Admission admission = this.getAdmission();

		if (!admission.isExitInited()) {
			return false;
		}

		boolean depositExist = false;
		boolean refundExist = false;

		if (admission.getPayments() != null) {
			for (Payment payment : admission.getPayments()) {
				if (payment.getPaymentType().getName().equals(type) && payment.getPaymentType().isRefundable()
						&& !payment.isRefund()) {
					depositExist = true;
				}
				if (payment.getPaymentType().getName().equals(type) && payment.getPaymentType().isRefundable()
						&& payment.isRefund()) {
					refundExist = true;
				}
			}
		}
		if (depositExist && !refundExist) {
			return true;
		}
		return false;
	}

	public boolean isRefundable(final String paymentType) {
		return this.paymentTypeDao.isRefundable(paymentType);
	}

	protected Payment createSecurityDeposit(final Integer securityDepositRefundAmount, final Receipt receipt) {
		Payment payment = new Payment();
		payment.setAmount(securityDepositRefundAmount);
		payment.setPaymentType(this.paymentTypeDao.find("name='Security Deposit'").get(0));
		payment.setReceipt(this.reloadReceipt(receipt));
		payment.setPayable(this.admission);
		return payment;
	}

	@Transactional
	public boolean securityDeposit(final Admission admission, final Integer securityDepositAmount, final Receipt receipt) {
		Payment payment = this.createSecurityDeposit(securityDepositAmount, receipt);
		this.paymentDao.persist(payment);
		return this.doPostDepositTask(receipt.getDate());
	}

	@Transactional
	public boolean securityDepositRefund(final Admission admission, final Integer securityDepositRefundAmount, final Receipt receipt) {
		Payment payment = this.createSecurityDeposit(securityDepositRefundAmount, receipt);
		payment.setRefund(true);
		this.paymentDao.persist(payment);
		return this.doPostRefundTask(receipt.getDate());
	}

	protected Payment createUtensilDeposit(final Integer utensilDepositAmount, final Receipt receipt) {
		Payment payment = new Payment();
		payment.setAmount(utensilDepositAmount);
		payment.setPaymentType(this.paymentTypeDao.find("name='Utensil Deposit'").get(0));
		payment.setReceipt(this.reloadReceipt(receipt));
		payment.setPayable(this.admission);
		return payment;
	}

	@Transactional
	public boolean utensilDeposit(final Admission admission, final Integer utensilDepositAmount, final Receipt receipt) {
		Payment payment = this.createUtensilDeposit(utensilDepositAmount, receipt);
		this.paymentDao.persist(payment);
		return this.doPostDepositTask(receipt.getDate());
	}

	@Transactional
	public boolean utensilDepositRefund(final Admission admission, final Integer utensilDepositAmount, final Receipt receipt) {
		Payment payment = this.createUtensilDeposit(utensilDepositAmount, receipt);
		payment.setRefund(true);
		this.paymentDao.persist(payment);
		return this.doPostRefundTask(receipt.getDate());
	}

	protected boolean doPostRefundTask(final Date date) {

		// Lets repopulate
		this.populate(this.getAdmission(), date);

		List<PaymentType> refundables = this.paymentTypeDao.find("refundable=true");

		this.logger.trace("Verifying if any refundables pending refund...");

		for (PaymentType paymentType : refundables) {
			// if any of refundables still requires refund, then refund needs to
			// be done first, so quit from here
			if (this.allowRefund(paymentType.getName())) {
				this.logger.trace(paymentType.getName() + " still pending refund");
				return false;
			}
		}

		Admission admission = this.getAdmission();

		admission.setDateOfExit(date);
		admission.setActive(false);

		this.admissionDao.persist(admission);

		this.admissionService.reload();
		this.dailyDepositAndRefundService.reload();

		return true;
	}

	/**
	 * Return true is deposit task is complete Task is complete if security
	 * deposit is done and if there is any material allocation then its deposit
	 * must be done as well
	 * 
	 * @param date
	 * @return
	 */
	protected boolean doPostDepositTask(final Date date) {

		// Lets repopulate
		this.populate(this.getAdmission(), date);

		Admission admission = this.getAdmission();

		boolean utensilDepositDone = false;
		boolean securityDepositDone = false;
		for (Payment payment : admission.getPayments()) {
			if (payment.getPaymentType().getName().equals("Utensil Deposit") && !payment.isRefund()) {
				utensilDepositDone = true;
			}
			if (payment.getPaymentType().getName().equals("Security Deposit") && !payment.isRefund()) {
				securityDepositDone = true;
			}
		}

		// TODO we should differentiate later between material and utensil
		// deposit and the sets etc

		if (securityDepositDone && ((admission.getMaterialSet() == null) || utensilDepositDone)) {

			admission.setActive(true);
			this.admissionDao.persist(admission);

			this.admissionService.reload();
			this.dailyDepositAndRefundService.reload();

			return true;
		}

		return false;
	}

	public void setReceiptsModel(final DataModel receiptsModel) {
		this.receiptsModel = receiptsModel;
	}

	public DataModel getReceiptsModel() {
		return this.receiptsModel;
	}

	public void selectReceipt() {

		this.selectedReceipt = this.receipts.get(this.receiptsModel.getRowIndex());

		if (!this.selectedReceipt.isSelected()) {
			this.selectedReceipt = null;
			return;
		}
		for (int i = 0; i < this.receipts.size(); i++) {
			if (i != this.receiptsModel.getRowIndex()) {
				this.receipts.get(i).setSelected(false);
			}
		}
	}

	public Receipt getSelectedReceipt() {
		return this.selectedReceipt;
	}

	public void completeDeposit() {
		Admission admission = this.admissionDao.findById(this.getAdmission().getId());

		admission.setActive(true);
		this.admissionDao.update(admission);

		this.admissionService.reload();
		this.dailyDepositAndRefundService.reload();
	}

	public void completeRefund(Date date) {

		Admission admission = this.admissionDao.findById(this.getAdmission().getId());

		admission.setDateOfExit(date);
		admission.setActive(false);

		this.admissionDao.update(admission);

		this.admissionService.reload();
		this.dailyDepositAndRefundService.reload();

	}
}
